Build propagator transfer functions on the device the data is on - #94
Merged
Conversation
Four propagators passed params.gpuSwitch into their cached transfer-function builders instead of asking where the field actually is: propagate_twoStepPolychrome Operators.py:271 propagate_scaledASP Operators.py:343 propagate_scaledPolychromeASP Operators.py:415 propagate_polychromeASP Operators.py:495 The switch is a global intent flag, not a fact about any particular array. Arrays are moved by BaseEngine._checkGPU and by the engines themselves, independently of when a transfer function is first built and cached, so the two can disagree -- and then a CPU transfer function meets a GPU field, or the reverse: TypeError: Unsupported type <class 'numpy.ndarray'> All four now use isGpuArray(fields). The other transfer-function builders in this file (Operators.py:101,147) already did exactly this; these four were the outliers. Tests. Every one of the four call sites is covered by a GPU-vs-CPU test that passes a device array while leaving params.gpuSwitch False, and each fails without this change. Three of them are polychrome and need nlambda > 1, so they live in tests/regression where the synthetic dataset can supply a spectral density; the single-wavelength ones are in tests/Operators. Also adds, for all eight propagators, output goldens; a round-trip identity test for the unitary ones; and a linearity test for all of them. test_object2detector previously called every propagator and asserted nothing. Round-trip identity is asserted only for the unitary propagators. asp and scaledasp are band-limited -- measured ||P(x)-x||/||x|| is 3.4e-01 and 5.4e-02, and while asp turns out to be a clean projection (idempotent to 3.1e-07), scaledasp is not (1.6e-03), so linearity is the property both actually share. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fourth in the series building a safety net before performance work. Follows #90, #91, and the
e3PIE fix.
The bug
Four propagators passed
params.gpuSwitchinto their cached transfer-function builders insteadof asking where the field actually is:
propagate_twoStepPolychromeOperators.py:271propagate_scaledASPOperators.py:343propagate_scaledPolychromeASPOperators.py:415propagate_polychromeASPOperators.py:495gpuSwitchis a global intent flag, not a fact about any particular array. Arrays get movedby
BaseEngine._checkGPUand by the engines themselves, independently of when a transferfunction is first built and cached — so the two can disagree. Then a CPU transfer function meets
a GPU field:
All four now use
isGpuArray(fields). Worth noting the other two transfer-function builders inthe same file (
Operators.py:101,147) already did this — these four were the outliers, nota new convention.
Verification
Each of the four call sites is covered by a GPU-vs-CPU test that passes a device array while
leaving
params.gpuSwitchFalse. RevertingOperators.pytomainfails all four:Three are polychrome and need
nlambda > 1, so they live intests/regression/where thesynthetic dataset can supply a spectral density; the single-wavelength ones are in
tests/Operators/.Other test coverage added
test_object2detectorpreviously called every propagator and asserted nothing. Now:P(ax) == aP(x), for all of themSuite goes 30 → 59 passing.
Why round-trip identity is not asserted for asp/scaledasp
They are band-limited — forward∘inverse discards out-of-band energy, so it is not the identity.
Measured
||P(x)-x||/||x||: 3.4e-01 for asp, 5.4e-02 for scaledasp.What survives differs between them, which is why neither is asserted:
aspscaledaspLinearity is the property both genuinely share; the numbers themselves are pinned by the
goldens.
Version
0.2.7→0.2.8, published automatically on merge.